﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using TrainSimulator.Interfaces;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using TrainSimulator;
using TrainSimulator.Model.Track;

namespace TrainSimulatorGUI.CustomControls
{
    public partial class SimulatorPanel : UserControl
    {
        public Simulator Simulator { get; set; }
        public Dictionary<ITrack, TrackGeometry> TracksAndGraphics { get; set; }
        private Bitmap _trackImage;
        private float _scale = 1;
        private float _margin = 25;
        private SizeF _trackPartSize;
        private PointF _offsetPoint;
        private PointF _minEndPoint;
        private PointF _maxEndPoint;

        public bool TrackInfo { get; set; }

        public SimulatorPanel()
        {
            InitializeComponent();
            // Width and height of each track part
            _trackPartSize = new SizeF(10, 6);
            // Show info about each track
            TrackInfo = true;
        }

        public void DrawSimulation(Simulator simulator)
        {
            this.Simulator = simulator;
            GenerateTrackImage();
            //GenerateRailRoad(simulator.Tracks); // Test drawing
        }

        private void GenerateTrackImage()
        {
            //this.SetStyle(ControlStyles.UserPaint, true);
            //this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.DoubleBuffer, true);
            TracksAndGraphics = new Dictionary<ITrack, TrackGeometry>();

            // Handle graphics - creates a graphic class to a track
            IEnumerator<ITrack> t = Simulator.Tracks.GetEnumerator();
            while (t.MoveNext())
            {
                TrackGeometry currentGraphic = null;
                TrackGeometry previousGraphic = (t.Current.Previous != null && t.Current.ID > t.Current.Previous.ID) ?
                    (TrackGeometry)TracksAndGraphics[t.Current.Previous] : null;

                // Use endpoint and (start angle + angle) from previous track to create current track
                if (previousGraphic != null)
                {
                    currentGraphic = TrackGeometry.Create(t.Current, previousGraphic.EndPoint, previousGraphic.EndAngle, t.Current.Angle, new Bitmap(DrawTrack()));
                }
                else // Else use current track instead
                {
                    currentGraphic = TrackGeometry.Create(t.Current, t.Current.StartPoint, t.Current.StartAngle, t.Current.Angle, new Bitmap(DrawTrack()));
                }

                // Updates min and max point to calculate offset
                CalculateEndPoints(currentGraphic);

                // Adds a key/value pair ( track / graphic ) - used by trains
                TracksAndGraphics.Add(t.Current, currentGraphic);
            }
            float g = _maxEndPoint.X - _minEndPoint.X;
            float scaleX = (picBox.Size.Width - 2 * _margin) / Math.Abs(_maxEndPoint.X - _minEndPoint.X);
            float scaleY = (picBox.Size.Height - 2 * _margin) / Math.Abs(_maxEndPoint.Y - _minEndPoint.Y);
            _scale = (scaleX < scaleY) ? scaleX : scaleY;

            _offsetPoint = new PointF()
            {
                X = _margin - _minEndPoint.X * _scale,
                Y = _margin - _minEndPoint.Y * _scale
            };
            DrawTracks();
        }
        private void CalculateEndPoints(TrackGeometry tg)
        {
            if (tg.EndPoint.X < _minEndPoint.X) _minEndPoint = new PointF(tg.EndPoint.X, _minEndPoint.Y);
            if (tg.EndPoint.X > _maxEndPoint.X) _maxEndPoint = new PointF(tg.EndPoint.X, _maxEndPoint.Y);
            if (tg.EndPoint.Y < _minEndPoint.Y) _minEndPoint = new PointF(_minEndPoint.X, tg.EndPoint.Y);
            if (tg.EndPoint.Y > _maxEndPoint.Y) _maxEndPoint = new PointF(_maxEndPoint.X, tg.EndPoint.Y);
        }

        #region Handle Tracks

        private void DrawTracks()
        {
            Bitmap tackImage = new Bitmap(this.Width, this.Height);
            using (Graphics g = Graphics.FromImage(tackImage))
            {
                g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                g.SmoothingMode = SmoothingMode.AntiAlias; // better quality ?
                //g.InterpolationMode = InterpolationMode.HighQualityBilinear;
                g.Clear(Color.Transparent);
                using (Pen p = new Pen(Color.Black, 2))
                {
                    var i = TracksAndGraphics.GetEnumerator();
                    while (i.MoveNext())
                    {

                        ITrack track = i.Current.Key;
                        TrackGeometry tg = i.Current.Value;
                        Bitmap trackImg = tg.TrackImage;
                        int numTextures = (int)((track.Length / trackImg.Height) * 2); // number of parts?
                        Matrix oldTransform = g.Transform;
                        for (int l = 0; l < numTextures; l++)
                        {
                            // Gets position of track part
                            double pos = track.Length * l / (numTextures - 1);

                            // Modify position from angle and sets a point
                            PointF position = new PointF();
                            position = tg.GetPosition(pos);

                            // Create offset from position to fit screen
                            PointF offsetPoint = new PointF()
                            {
                                X = position.X * _scale + _offsetPoint.X,
                                Y = position.Y * _scale + _offsetPoint.Y
                            };

                            // Calculate angle of track part
                            // Then use angle and offset to transform further drawing
                            Matrix transform = new Matrix();
                            transform.RotateAt(
                                (float)(tg.RadianToDegree(tg.GetAngle(pos))), offsetPoint);
                            g.Transform = transform;

                            // Calculate size of part track
                            SizeF size = new SizeF(i.Current.Value.TrackImage.Width * _scale, i.Current.Value.TrackImage.Height * _scale);
                            // Sets location and size of part track - then draw the part
                            RectangleF drawingLocation = new RectangleF(new PointF(offsetPoint.X - size.Width / 2f, offsetPoint.Y), size);
                            g.DrawImage(tg.TrackImage, drawingLocation);

                            // Draw information about id, start and end location of each track
                            #region Track Information
                            if (TrackInfo) // if information is enabled
                            {
                                p.Color = Color.LightBlue;
                                if (l == 0)
                                {
                                    g.DrawLine(p,
                                      offsetPoint.X - size.Width * 2, offsetPoint.Y,
                                      offsetPoint.X + size.Width * 2, offsetPoint.Y);
                                    g.DrawString(i.Current.Key.ID.ToString(), new Font("Arial", 12),
                                            new SolidBrush(Color.Black), new PointF(offsetPoint.X - size.Width * 2, offsetPoint.Y));
                                }
                                p.Color = Color.Red;
                                if (l == numTextures - 1)
                                    g.DrawLine(p,
                                      offsetPoint.X - size.Width * 2, offsetPoint.Y,
                                      offsetPoint.X + size.Width * 2, offsetPoint.Y);
                            }
                            #endregion

                            // Reset transformation
                            g.Transform = oldTransform;
                        }
                    }
                }
            }
            this._trackImage = tackImage;
            picBox.Image = tackImage;
        }

        private Bitmap DrawTrack()
        {
            int w = 20;
            int h = 8;
            int margin = 5;
            Bitmap trackImg = new Bitmap(w, h, PixelFormat.Format32bppArgb);
            using (Graphics g = Graphics.FromImage(trackImg))
            {
                using (Pen p = new Pen(Color.DarkGoldenrod, 3))
                {
                    g.Clear(Color.Transparent);

                    // Draw concret underneath
                    Rectangle rect = new Rectangle(-margin, 0, w + margin, h);
                    g.FillRectangle(new SolidBrush(Color.PaleGoldenrod), rect);

                    // Draw a wood pile
                    g.DrawLine(p, margin, 2, w - margin, 2);

                    // Draw two tracks ontop 
                    p.Width = 1;
                    p.Color = Color.DarkSlateGray;
                    g.DrawLine(p, 2 + margin, 0, 2 + margin, (float)h);
                    g.DrawLine(p, 7 + margin, 0, 7 + margin, h);
                }
            }
            return trackImg;
        }

        #endregion

        #region Handling Trains

        private void DrawTrains(Graphics g)
        {
            foreach (var t in Simulator.Trains)
            {
                double rearPos, enginePos = t.Engine.TrackPosition; // first position
                ITrack track = t.CurrentTrack; // first track
                ITrack prevTrack = track.Previous;
                TrackGeometry tg = TracksAndGraphics[track]; // first grapich

                foreach (var w in t.Wagons)
                {
                    PointF frontPoint = tg.GetPosition(enginePos);
                    PointF offsetPoint = new PointF()
                    {
                        X = frontPoint.X * _scale + _offsetPoint.X,
                        Y = frontPoint.Y * _scale + _offsetPoint.Y
                    };
                    Matrix oldTransform = g.Transform;
                    Matrix transform = g.Transform.Clone(); //No changes to original transform
                    transform.RotateAt((float)(tg.RadianToDegree(tg.GetAngle(enginePos))), offsetPoint);
                    g.Transform = transform;

                    // Draw the wagon
                    Bitmap img = new Bitmap(DrawWagon(w.Length));
                    // Front of the wagon
                    PointF frontCorner = new PointF(offsetPoint.X - 1 - img.Width * _scale / 2, offsetPoint.Y - img.Height * _scale / 2);
                    g.DrawImage(img, frontCorner.X, frontCorner.Y, img.Width * _scale, img.Height * _scale);
                    g.DrawString("T"+t.ID+"-"+w.ID, this.Font, new SolidBrush(Color.Blue), 
                        new PointF(frontCorner.X - 30, frontCorner.Y));

                    // Reset transformation
                    g.Transform = oldTransform;

                    // prepare position for next wagon
                    //int direction = -TrackDirection(track, preTrack); // track direction for next wagon
                    rearPos = enginePos - (1 - w.Length);

                    bool found = true;
                    if (rearPos < 0) found = false;
                    if (rearPos > track.Length) found = false;
                    while (!found)
                    {
                        ITrack temp = track;
                        track = track.NextTrack;
                        prevTrack = temp;

                        if (track == null)
                        {
                            found = true;
                            track = prevTrack;
                        }
                        if (rearPos < 0)
                        {
                            rearPos = track.Length + rearPos;
                            if (rearPos >= 0) found = true;
                        }
                        if (rearPos > prevTrack.Length)
                        {
                            rearPos = rearPos - prevTrack.Length;
                            if (rearPos <= track.Length) found = true;
                        }
                        if (found)
                        {
                            tg = TracksAndGraphics[track];
                        }
                    }
                    if (!found) rearPos = 0;
                        enginePos = rearPos;
                }
            }
        }

        private Bitmap DrawWagon(double length)
        {
            int w = 10;
            int h = (int)length;
            Bitmap trainImg = new Bitmap(w, h);
            using (Graphics g = Graphics.FromImage(trainImg))
            {
                g.FillRectangle(new SolidBrush(Color.WhiteSmoke), 0, 0, w, h);
                g.DrawRectangle(new Pen(Color.Silver), 0, 0, w, h);
                g.DrawRectangle(new Pen(Color.White), 1, 1, w, h);
                g.DrawRectangle(new Pen(Color.Silver), -1, -1, w, h);
            }
            return trainImg;
        }

        #endregion

        /// <summary>
        /// Updates the simulation panel at runtime
        /// </summary>
        private void picBox_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            if (_trackImage != null)
            {
                // Reuse track image
                g.DrawImage(_trackImage, 0, 0);
                // Update train image
                DrawTrains(g);
            }
        }

        #region early test

        private void GenerateRailRoad(List<ITrack> tracks)
        {
            PointF startPoint = new PointF(0, 0);
            Bitmap railRoadImg = new Bitmap(picBox.Width, picBox.Height, PixelFormat.Format32bppArgb);

            using (var g = Graphics.FromImage(railRoadImg))
            {
                for (int i = 0; i < tracks.Count; i++)
                {
                    // Sets startpoint - endpoint is defined while drawing
                    tracks[i].StartPoint = startPoint;
                    Image img = DrawStraightTrack(tracks[i]);
                    g.DrawImage(img, startPoint);

                    startPoint = new PointF(tracks[i].EndPoint.X - _trackPartSize.Width, tracks[i].EndPoint.Y);
                }

            }

            picBox.Image = railRoadImg;
            //picBox.Update();
        }
        private Image DrawStraightTrack(ITrack track) // horisontal/vertical ?
        {

            Bitmap trackImg = new Bitmap((int)_trackPartSize.Width, (int)track.Length, PixelFormat.Format32bppArgb);

            using (var p = new Pen(Color.Brown, 1))
            {
                using (var g = Graphics.FromImage(trackImg))
                {
                    PointF from, to;
                    g.Clear(Color.Transparent);

                    // Draw wood blocks
                    for (int i = 0; i <= track.Length; i += 2)
                    {
                        from = new Point(0, i);
                        to = new PointF(_trackPartSize.Width, i);
                        g.DrawLine(p, from, to);
                    }

                    // Now calculate endpoint
                    track.EndPoint =
                        new PointF(track.StartPoint.X + trackImg.Size.Width, track.StartPoint.Y + trackImg.Size.Height);

                    // Draw two tracks on top
                    p.Color = Color.DarkSlateGray;
                    from = new PointF(_trackPartSize.Width / 4, 0);
                    to = new PointF(_trackPartSize.Width / 4, (float)track.Length);
                    g.DrawLine(p, from, to);
                    from = new PointF((_trackPartSize.Width / -4 + 1) + _trackPartSize.Width - 1, 0);
                    to = new PointF((_trackPartSize.Width / -4 + 1) + _trackPartSize.Width - 1, (float)track.Length);
                    g.DrawLine(p, from, to);

                    // Draw split line after track
                    p.Color = Color.Black;
                    p.Width = 2;
                    from = new PointF(0, (float)track.Length);
                    to = new PointF(_trackPartSize.Width, (float)track.Length);
                    g.DrawLine(p, from, to);
                }
            }
            return trackImg;
        }

        #endregion
    }
}
